package com.gs.protobuf.utils;

import com.gs.common.tool.utils.OsUtil;
import com.gs.common.tool.utils.PoJoUtil;
import com.gs.common.tool.utils.StringUtil;
import com.gs.protobuf.consts.ProtocFileConst;
import com.gs.protobuf.enums.ProtoVersionEnum;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.BiConsumer;

public class ProtocCompilerUtil {

    private static final String FILE_SUFFIX = "java";

    /**
     * 编译 .proto 并强制修改包名，支持日志回调
     */
    public static void compile(File protoFile, File outputDir, String protoVersion, String targetPackage, BiConsumer<Boolean, String> log) throws Exception {
        //log.accept("🔍 正在准备内置 protoc 编译器...");

        String protocResource = getProtocResourceName();
        InputStream is = ProtocCompilerUtil.class.getResourceAsStream(String.format("/protoc/%s/%s", protoVersion, protocResource));
        if (is == null) {
            log.accept(false, "未找到内置 protoc: " + protocResource);
            return;
        }

        Path tempProtoc = Files.createTempFile("protoc_", OsUtil.isWindows() ? ".exe" : "");
        tempProtoc.toFile().deleteOnExit();

        try (FileOutputStream fos = new FileOutputStream(tempProtoc.toFile())) {
            byte[] buffer = new byte[4096];
            int read;
            while ((read = is.read(buffer)) != -1) {
                fos.write(buffer, 0, read);
            }
        }

        if (!OsUtil.isWindows()) {
            tempProtoc.toFile().setExecutable(true);
        }
        log.accept(true, "✅ proto协议版本: " + ProtoVersionEnum.getFullName(protoVersion) + "\n");
        //log.accept("⚙️  执行 protoc 命令...");
        String protoFileDir = protoFile.getParent();
        ProcessBuilder pb = new ProcessBuilder(
                tempProtoc.toString(),
                "--proto_path", protoFileDir,
                "--java_out", outputDir.getAbsolutePath(),
                protoFile.getName()
        );
        pb.redirectErrorStream(true);
        Process process = pb.start();
        // 实时读取输出流并写入日志
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (!line.trim().isEmpty()) {
                    log.accept(true, "protoc: " + line);
                }
            }
        }
        int exitCode = process.waitFor();
        if (exitCode != 0) {
            log.accept(false, "protoc 执行失败，退出码: " + exitCode);
            return;
        }
        //log.accept("✅ protoc 执行完成，开始修改包名...");
        //扫描输出目录下的Java文件
        File[] javaFiles = outputDir.listFiles((dir, name) -> name.endsWith(".java"));
        if (javaFiles == null || javaFiles.length == 0) {
            log.accept(false, "未生成任何 Java 文件");
            return;
        }
        //筛选proto文件对应的java类
        String className = PoJoUtil.lineToHump(protoFile.getName().substring(0, protoFile.getName().lastIndexOf("."))).toLowerCase();
        File updFile = Arrays.stream(javaFiles).filter(d -> d.getName().toLowerCase().startsWith(className)).findAny().orElse(null);
        if (Objects.isNull(updFile)) {
            log.accept(false, String.format("%s下，未找到生成的Java文件", outputDir.getAbsolutePath()));
            return;
        }
        //如果没传包路径，默认使用proto文件默认路径
        if (StringUtil.isEmpty(targetPackage)) {
            targetPackage = protoFileDir.substring(protoFileDir.indexOf(FILE_SUFFIX) + FILE_SUFFIX.length() + 1).replaceAll(OsUtil.getFileSep(), ".");
        }
        //添加包导入
        updatePackageInJavaFile(updFile, targetPackage);
        log.accept(true, String.format("🎉 %s文件生成完毕！", protoFile.getName()));
        log.accept(true, "✅ java文件路径: \n" + updFile.getAbsolutePath() + "\n");
    }

    private static void updatePackageInJavaFile(File javaFile, String newPackage) throws IOException {
        List<String> lines = Files.readAllLines(javaFile.toPath(), StandardCharsets.UTF_8);
        List<String> newLines = new java.util.ArrayList<>();
        boolean hasPackage = !lines.isEmpty() && lines.get(0).trim().startsWith("package ");
        String newPackageLine = "package " + newPackage + ";";
        if (hasPackage) {
            newLines.add(newPackageLine);
            // 跳过原 package 行
            for (int i = 1; i < lines.size(); i++) {
                newLines.add(lines.get(i));
            }
        } else {
            newLines.add(newPackageLine);
            newLines.addAll(lines);
        }
        Files.write(javaFile.toPath(), newLines, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING);
    }

    private static String getProtocResourceName() {
        if (OsUtil.isWindows()) return ProtocFileConst.windows_name;
        else if (OsUtil.isMac()) return ProtocFileConst.macos_name;
        else return ProtocFileConst.linux_name;
    }
}